package com.atom.module.mvvm.extren

import android.view.View
import androidx.annotation.CheckResult
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate

@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
 fun <T : RecyclerView.Adapter<out RecyclerView.ViewHolder>> T.dataChanges() = callbackFlow<T> {
    checkMainThread()
    val observer = object : RecyclerView.AdapterDataObserver() {
        override fun onChanged() {
            trySend(this@dataChanges)
        }
    }
    registerAdapterDataObserver(observer)
    awaitClose { unregisterAdapterDataObserver(observer) }
}
    .conflate()
    .asInitialValueFlow { this }


/**
 * Create a [Flow] of child attach state change events on the [RecyclerView] instance
 * where the value emitted is one of the 2 event types:
 * [RecyclerViewChildAttachStateChangeEvent.Attached],
 * [RecyclerViewChildAttachStateChangeEvent.Detached]
 *
 * Note: Created flow keeps a strong reference to the [RecyclerView] instance
 * until the coroutine that launched the flow collector is cancelled.
 *
 * Example of usage:
 *
 * ```
 * recyclerView.childAttachStateChangeEvents()
 *     .onEach { event ->
 *          when(event) {
 *              is RecyclerViewChildAttachStateChangeEvent.Attached -> {
 *                  // handle attached event
 *              }
 *              is RecyclerViewChildAttachStateChangeEvent.Detached -> {
 *                  // handle detached event
 *              }
 *          }
 *     }
 *     .launchIn(uiScope)
 * ```
 */
@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
 fun RecyclerView.childAttachStateChangeEvents() = callbackFlow<RecyclerViewChildAttachStateChangeEvent> {
    checkMainThread()
    val listener = object : RecyclerView.OnChildAttachStateChangeListener {
        override fun onChildViewAttachedToWindow(childView: View) {
            trySend(
                RecyclerViewChildAttachStateChangeEvent.Attached(
                    view = this@childAttachStateChangeEvents,
                    child = childView
                )
            )
        }

        override fun onChildViewDetachedFromWindow(childView: View) {
            trySend(
                RecyclerViewChildAttachStateChangeEvent.Detached(
                    view = this@childAttachStateChangeEvents,
                    child = childView
                )
            )
        }
    }
    addOnChildAttachStateChangeListener(listener)
    awaitClose { removeOnChildAttachStateChangeListener(listener) }
}.conflate()

 sealed class RecyclerViewChildAttachStateChangeEvent {
     abstract val view: RecyclerView
     abstract val child: View

     data class Attached(
        override val view: RecyclerView,
        override val child: View
    ) : RecyclerViewChildAttachStateChangeEvent()

     data class Detached(
        override val view: RecyclerView,
        override val child: View
    ) : RecyclerViewChildAttachStateChangeEvent()
}

/**
 * Create a [Flow] of fling events on the [RecyclerView] instance.
 *
 * @param handled function to be invoked with each value to determine the return value of
 * the underlying [RecyclerView.OnFlingListener]. Note that the [Flow] will only emit when this function
 * evaluates to true.
 *
 * Note: Created flow keeps a strong reference to the [RecyclerView] instance
 * until the coroutine that launched the flow collector is cancelled.
 *
 * Example of usage:
 *
 * ```
 * recyclerView.flingEvents { it.velocityX != 0 }
 *     .onEach { event ->
 *          // handle fling event
 *     }
 *     .launchIn(uiScope)
 * ```
 */
@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
 fun RecyclerView.flingEvents(handled: (FlingEvent) -> Boolean = { true }) = callbackFlow<FlingEvent> {
    checkMainThread()
    val listener = object : RecyclerView.OnFlingListener() {
        override fun onFling(velocityX: Int, velocityY: Int): Boolean {
            val event = FlingEvent(
                view = this@flingEvents,
                velocityX = velocityX,
                velocityY = velocityY
            )
            return if (handled(event)) {
                trySend(event)
                true
            } else {
                false
            }
        }
    }
    onFlingListener = listener
    awaitClose { onFlingListener = null }
}.conflate()

 data class FlingEvent(
     val view: RecyclerView,
     val velocityX: Int,
     val velocityY: Int
)

/**
 * Create a [Flow] of scroll events on the [RecyclerView] instance.
 *
 * Note: Created flow keeps a strong reference to the [RecyclerView] instance
 * until the coroutine that launched the flow collector is cancelled.
 *
 * Example of usage:
 *
 * ```
 * recyclerView.scrollEvents()
 *     .onEach { event ->
 *          // handle recycler view scroll event
 *     }
 *     .launchIn(uiScope)
 * ```
 */
@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
 fun RecyclerView.scrollEvents() = callbackFlow<RecyclerViewScrollEvent>  {
    checkMainThread()
    val listener = object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            trySend(
                RecyclerViewScrollEvent(
                    view = this@scrollEvents,
                    dx = dx,
                    dy = dy
                )
            )
        }
    }
    addOnScrollListener(listener)
    awaitClose { removeOnScrollListener(listener) }
}.conflate()

 data class RecyclerViewScrollEvent(
     val view: RecyclerView,
     val dx: Int,
     val dy: Int
)

/**
 * Create a [Flow] of scroll state change events on the [RecyclerView] instance
 * where the value emitted can be one of [SCROLL_STATE_IDLE], [SCROLL_STATE_DRAGGING]} or [SCROLL_STATE_SETTLING].
 *
 * Note: Created flow keeps a strong reference to the [RecyclerView] instance
 * until the coroutine that launched the flow collector is cancelled.
 *
 * Example of usage:
 *
 * ```
 * recyclerView.scrollStateChanges()
 *     .onEach { state ->
 *          // handle state
 *     }
 *     .launchIn(uiScope)
 * ```
 */
@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
 fun RecyclerView.scrollStateChanges() = callbackFlow<Int> {
    checkMainThread()
    val listener = object : RecyclerView.OnScrollListener() {
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            trySend(newState)
        }
    }
    addOnScrollListener(listener)
    awaitClose { removeOnScrollListener(listener) }
}.conflate()


